var _plugins = {};

function _plugin(name, fn) {
  if (name === undefined) {
    return _plugins;
  }
  if (!fn) {
    return _plugins[name];
  }
  _plugins[name] = fn;
}

var _language = {};

function _parseLangKey(key) {
  var match,
    ns = "core";
  if ((match = /^(\w+)\.(\w+)$/.exec(key))) {
    ns = match[1];
    key = match[2];
  }
  return { ns: ns, key: key };
}
/**
	@example
	K.lang('about'); //get core.about
	K.lang('about.version'); // get about.version
	K.lang('about.').version; // get about.version
	K.lang('about', 'en'); //get English core.about
	K.lang({
		core.about : '关于',
		about.version : '4.0'
	}, 'zh-CN'); //add language
*/
function _lang(mixed, langType) {
  langType = langType === undefined ? K.options.langType : langType;
  if (typeof mixed === "string") {
    if (!_language[langType]) {
      return "no language";
    }
    var pos = mixed.length - 1;
    if (mixed.substr(pos) === ".") {
      return _language[langType][mixed.substr(0, pos)];
    }
    var obj = _parseLangKey(mixed);
    return _language[langType][obj.ns][obj.key];
  }
  _each(mixed, function(key, val) {
    var obj = _parseLangKey(key);
    if (!_language[langType]) {
      _language[langType] = {};
    }
    if (!_language[langType][obj.ns]) {
      _language[langType][obj.ns] = {};
    }
    _language[langType][obj.ns][obj.key] = val;
  });
}

// 当前range为图片时返回KNode，否则返回undefined
function _getImageFromRange(range, fn) {
  if (range.collapsed) {
    return;
  }
  range = range.cloneRange().up();
  var sc = range.startContainer,
    so = range.startOffset;
  if (!_WEBKIT && !range.isControl()) {
    return;
  }
  var img = K(sc.childNodes[so]);
  if (!img || img.name != "img") {
    return;
  }
  if (fn(img)) {
    return img;
  }
}

function _bindContextmenuEvent() {
  var self = this,
    doc = self.edit.doc;
  K(doc).contextmenu(function(e) {
    if (self.menu) {
      self.hideMenu();
    }
    if (!self.useContextmenu) {
      e.preventDefault();
      return;
    }
    if (self._contextmenus.length === 0) {
      return;
    }
    var maxWidth = 0,
      items = [];
    _each(self._contextmenus, function() {
      if (this.title == "-") {
        items.push(this);
        return;
      }
      if (this.cond && this.cond()) {
        items.push(this);
        if (this.width && this.width > maxWidth) {
          maxWidth = this.width;
        }
      }
    });
    while (items.length > 0 && items[0].title == "-") {
      items.shift();
    }
    while (items.length > 0 && items[items.length - 1].title == "-") {
      items.pop();
    }
    var prevItem = null;
    _each(items, function(i) {
      if (this.title == "-" && prevItem.title == "-") {
        delete items[i];
      }
      prevItem = this;
    });
    if (items.length > 0) {
      e.preventDefault();
      var pos = K(self.edit.iframe).pos(),
        menu = _menu({
          x: pos.x + e.clientX,
          y: pos.y + e.clientY,
          width: maxWidth,
          css: { visibility: "hidden" },
          shadowMode: self.shadowMode
        });
      _each(items, function() {
        if (this.title) {
          menu.addItem(this);
        }
      });
      // 下拉菜单超过可视区域时调整菜单位置
      var docEl = _docElement(menu.doc),
        menuHeight = menu.div.height();
      if (e.clientY + menuHeight >= docEl.clientHeight - 100) {
        menu.pos(menu.x, _removeUnit(menu.y) - menuHeight);
      }
      menu.div.css("visibility", "visible");
      self.menu = menu;
    }
  });
}

function _bindNewlineEvent() {
  var self = this,
    doc = self.edit.doc,
    newlineTag = self.newlineTag;
  if (_IE && newlineTag !== "br") {
    return;
  }
  if (_GECKO && _V < 3 && newlineTag !== "p") {
    return;
  }
  if (_OPERA && _V < 9) {
    return;
  }
  var brSkipTagMap = _toMap("h1,h2,h3,h4,h5,h6,pre,li"),
    pSkipTagMap = _toMap("p,h1,h2,h3,h4,h5,h6,pre,li,blockquote");
  // 取得range的block标签名
  function getAncestorTagName(range) {
    var ancestor = K(range.commonAncestor());
    while (ancestor) {
      if (ancestor.type == 1 && !ancestor.isStyle()) {
        break;
      }
      ancestor = ancestor.parent();
    }
    return ancestor.name;
  }
  K(doc).keydown(function(e) {
    if (e.which != 13 || e.shiftKey || e.ctrlKey || e.altKey) {
      return;
    }
    self.cmd.selection();
    var tagName = getAncestorTagName(self.cmd.range);
    if (tagName == "marquee" || tagName == "select") {
      return;
    }
    // br
    if (newlineTag === "br" && !brSkipTagMap[tagName]) {
      e.preventDefault();
      self.insertHtml("<br />" + (_IE && _V < 9 ? "" : "\u200B"));
      return;
    }
    // p
    if (!pSkipTagMap[tagName]) {
      _nativeCommand(doc, "formatblock", "<p>");
    }
  });
  K(doc).keyup(function(e) {
    if (e.which != 13 || e.shiftKey || e.ctrlKey || e.altKey) {
      return;
    }
    if (newlineTag == "br") {
      return;
    }
    if (_GECKO) {
      var root = self.cmd.commonAncestor("p");
      var a = self.cmd.commonAncestor("a");
      if (a && a.text() == "") {
        a.remove(true);
        self.cmd.range.selectNodeContents(root[0]).collapse(true);
        self.cmd.select();
      }
      return;
    }
    self.cmd.selection();
    var tagName = getAncestorTagName(self.cmd.range);
    if (tagName == "marquee" || tagName == "select") {
      return;
    }
    if (!pSkipTagMap[tagName]) {
      _nativeCommand(doc, "formatblock", "<p>");
    }
    // [WEBKIT] 将DIV改成P
    var div = self.cmd.commonAncestor("div");
    if (div) {
      var p = K("<p></p>"),
        child = div[0].firstChild;
      while (child) {
        var next = child.nextSibling;
        p.append(child);
        child = next;
      }
      div.before(p);
      div.remove();
      self.cmd.range.selectNodeContents(p[0]);
      self.cmd.select();
    }
  });
}

function _bindTabEvent() {
  var self = this,
    doc = self.edit.doc;
  K(doc).keydown(function(e) {
    if (e.which == 9) {
      e.preventDefault();
      if (self.afterTab) {
        self.afterTab.call(self, e);
        return;
      }
      var cmd = self.cmd,
        range = cmd.range;
      range.shrink();
      // Bugfix #271: 回车，按下tab键，光标在下一行显示
      if (range.collapsed && range.startContainer.nodeType == 1) {
        range.insertNode(K("@&nbsp;", doc)[0]);
        cmd.select();
      }
      self.insertHtml("&nbsp;&nbsp;&nbsp;&nbsp;");
    }
  });
}

function _bindFocusEvent() {
  var self = this;
  K(self.edit.textarea[0], self.edit.win)
    .focus(function(e) {
      if (self.afterFocus) {
        self.afterFocus.call(self, e);
      }
    })
    .blur(function(e) {
      if (self.afterBlur) {
        self.afterBlur.call(self, e);
      }
    });
}

function _removeBookmarkTag(html) {
  return _trim(
    html.replace(
      /<span [^>]*id="?__kindeditor_bookmark_\w+_\d+__"?[^>]*><\/span>/gi,
      ""
    )
  );
}

function _removeTempTag(html) {
  return html.replace(
    /<div[^>]+class="?__kindeditor_paste__"?[^>]*>[\s\S]*?<\/div>/gi,
    ""
  );
}

function _addBookmarkToStack(stack, bookmark) {
  if (stack.length === 0) {
    stack.push(bookmark);
    return;
  }
  var prev = stack[stack.length - 1];
  if (_removeBookmarkTag(bookmark.html) !== _removeBookmarkTag(prev.html)) {
    stack.push(bookmark);
  }
}

// undo: _undoToRedo.call(this, undoStack, redoStack);
// redo: _undoToRedo.call(this, redoStack, undoStack);
function _undoToRedo(fromStack, toStack) {
  var self = this,
    edit = self.edit,
    body = edit.doc.body,
    range,
    bookmark;
  if (fromStack.length === 0) {
    return self;
  }
  if (edit.designMode) {
    range = self.cmd.range;
    bookmark = range.createBookmark(true);
    bookmark.html = body.innerHTML;
  } else {
    bookmark = {
      html: body.innerHTML
    };
  }
  _addBookmarkToStack(toStack, bookmark);
  var prev = fromStack.pop();
  if (
    _removeBookmarkTag(bookmark.html) === _removeBookmarkTag(prev.html) &&
    fromStack.length > 0
  ) {
    prev = fromStack.pop();
  }
  if (edit.designMode) {
    edit.html(prev.html);
    if (prev.start) {
      range.moveToBookmark(prev);
      self.select();
    }
  } else {
    K(body).html(_removeBookmarkTag(prev.html));
  }
  return self;
}

function KEditor(options) {
  var self = this;
  // save original options
  self.options = {};
  function setOption(key, val) {
    if (KEditor.prototype[key] === undefined) {
      self[key] = val;
    }
    self.options[key] = val;
  }
  // set options from param
  _each(options, function(key, val) {
    setOption(key, options[key]);
  });
  // set options from default setting
  _each(K.options, function(key, val) {
    if (self[key] === undefined) {
      setOption(key, val);
    }
  });
  var se = K(self.srcElement || "<textarea/>");
  if (!self.width) {
    self.width = se[0].style.width || se.width();
  }
  if (!self.height) {
    self.height = se[0].style.height || se.height();
  }
  setOption("width", _undef(self.width, self.minWidth));
  setOption("height", _undef(self.height, self.minHeight));
  setOption("width", _addUnit(self.width));
  setOption("height", _addUnit(self.height));
  if (_MOBILE && (!_IOS || _V < 534)) {
    self.designMode = false;
  }
  self.srcElement = se;
  self.initContent = "";
  self.plugin = {};
  self.isCreated = false;
  // private properties
  self._handlers = {};
  self._contextmenus = [];
  self._undoStack = [];
  self._redoStack = [];
  self._firstAddBookmark = true;
  self.menu = self.contextmenu = null;
  self.dialogs = [];
}

KEditor.prototype = {
  lang: function(mixed) {
    return _lang(mixed, this.langType);
  },
  loadPlugin: function(name, fn) {
    var self = this;
    var _pluginStatus = this._pluginStatus;
    if (!_pluginStatus) {
      _pluginStatus = this._pluginStatus = {};
    }

    if (_plugins[name]) {
      // JS加载中，等待JS加载完成
      if (!_isFunction(_plugins[name])) {
        setTimeout(function() {
          self.loadPlugin(name, fn);
        }, 100);
        return self;
      }

      // JS加载完成，避免初始化多次
      if (!_pluginStatus[name]) {
        _plugins[name].call(self, KindEditor);
        _pluginStatus[name] = "inited";
      }

      if (fn) {
        fn.call(self);
      }
      return self;
    }
    // 还没加载相关plugin，动态加载
    _plugins[name] = "loading";
    _loadScript(
      self.pluginsPath +
        name +
        "/" +
        name +
        ".js?ver=" +
        encodeURIComponent(K.DEBUG ? _TIME : _VERSION),
      function() {
        // Fix bug: https://github.com/kindsoft/kindeditor/issues/105
        setTimeout(function() {
          if (_plugins[name]) {
            self.loadPlugin(name, fn);
          }
        }, 0);
      }
    );
    return self;
  },
  handler: function(key, fn) {
    var self = this;
    if (!self._handlers[key]) {
      self._handlers[key] = [];
    }
    if (_isFunction(fn)) {
      self._handlers[key].push(fn);
      return self;
    }
    _each(self._handlers[key], function() {
      fn = this.call(self, fn);
    });
    return fn;
  },
  clickToolbar: function(name, fn) {
    var self = this,
      key = "clickToolbar" + name;
    if (fn === undefined) {
      if (self._handlers[key]) {
        return self.handler(key);
      }
      self.loadPlugin(name, function() {
        self.handler(key);
      });
      return self;
    }
    return self.handler(key, fn);
  },
  updateState: function() {
    var self = this;
    _each(
      (
        "justifyleft,justifycenter,justifyright,justifyfull,insertorderedlist,insertunorderedlist," +
        "subscript,superscript,bold,italic,underline,strikethrough"
      ).split(","),
      function(i, name) {
        self.cmd.state(name)
          ? self.toolbar.select(name)
          : self.toolbar.unselect(name);
      }
    );
    return self;
  },
  addContextmenu: function(item) {
    this._contextmenus.push(item);
    return this;
  },
  afterCreate: function(fn) {
    return this.handler("afterCreate", fn);
  },
  beforeRemove: function(fn) {
    return this.handler("beforeRemove", fn);
  },
  beforeGetHtml: function(fn) {
    return this.handler("beforeGetHtml", fn);
  },
  beforeSetHtml: function(fn) {
    return this.handler("beforeSetHtml", fn);
  },
  afterSetHtml: function(fn) {
    return this.handler("afterSetHtml", fn);
  },
  create: function() {
    var self = this,
      fullscreenMode = self.fullscreenMode;
    if (self.isCreated) {
      return self;
    }

    if (self.srcElement.data("kindeditor")) {
      return self;
    }
    self.srcElement.data("kindeditor", "true");

    if (fullscreenMode) {
      _docElement().style.overflow = "hidden";
    } else {
      _docElement().style.overflow = "";
    }
    var width = fullscreenMode ? _docElement().clientWidth + "px" : self.width,
      height = fullscreenMode ? _docElement().clientHeight + "px" : self.height;
    // 校正IE6和IE7的高度
    if ((_IE && _V < 8) || _QUIRKS) {
      height = _addUnit(_removeUnit(height) + 2);
    }
    var container = (self.container = K(self.layout));
    if (fullscreenMode) {
      K(document.body).append(container);
    } else {
      self.srcElement.before(container);
    }
    var toolbarDiv = K(".toolbar", container),
      editDiv = K(".edit", container),
      statusbar = (self.statusbar = K(".statusbar", container));
    container
      .removeClass("container")
      .addClass("ke-container ke-container-" + self.themeType)
      .css("width", width);
    if (fullscreenMode) {
      container.css({
        position: "absolute",
        left: 0,
        top: 0,
        "z-index": 811211
      });
      if (!_GECKO) {
        self._scrollPos = _getScrollPos();
      }
      window.scrollTo(0, 0);
      // bugfix: [WEBKIT] 高度为0时在dialog里无法粘贴文本
      K(document.body).css({
        height: "1px",
        overflow: "hidden"
      });
      K(document.body.parentNode).css("overflow", "hidden");
      self._fullscreenExecuted = true;
    } else {
      if (self._fullscreenExecuted) {
        // 恢复文档高度
        K(document.body).css({
          height: "",
          overflow: ""
        });
        K(document.body.parentNode).css("overflow", "");
      }
      if (self._scrollPos) {
        window.scrollTo(self._scrollPos.x, self._scrollPos.y);
      }
    }
    // create toolbar
    var htmlList = [];
    K.each(self.items, function(i, name) {
      if (name == "|") {
        htmlList.push('<span class="ke-inline-block ke-separator"></span>');
      } else if (name == "/") {
        htmlList.push('<div class="ke-hr"></div>');
      } else {
        htmlList.push(
          '<span class="ke-outline" data-name="' +
            name +
            '" title="' +
            self.lang(name) +
            '" unselectable="on">'
        );
        htmlList.push(
          '<span class="ke-toolbar-icon ke-icon-' +
            name +
            '" unselectable="on">&#x' +
            self.fonts[name] +
            ";</span></span>"
        );
      }
    });
    var toolbar = (self.toolbar = _toolbar({
      src: toolbarDiv,
      html: htmlList.join(""),
      noDisableItems: self.noDisableItems,
      click: function(e, name) {
        e.stop();
        if (self.menu) {
          var menuName = self.menu.name;
          self.hideMenu();
          if (menuName === name) {
            return;
          }
        }
        self.clickToolbar(name);
      }
    }));
    // create edit
    var editHeight = _removeUnit(height) - toolbar.div.height();
    var edit = (self.edit = _edit({
      height:
        editHeight > 0 && _removeUnit(height) > self.minHeight
          ? editHeight
          : self.minHeight,
      src: editDiv,
      srcElement: self.srcElement,
      designMode: self.designMode,
      themesPath: self.themesPath,
      bodyClass: self.bodyClass,
      cssPath: self.cssPath,
      cssData: self.cssData,
      beforeGetHtml: function(html) {
        html = self.beforeGetHtml(html);
        // Bugfix: 浏览器后退产生__kindeditor_bookmark_start_0__
        html = _removeBookmarkTag(_removeTempTag(html));
        return _formatHtml(
          html,
          self.filterMode ? self.htmlTags : null,
          self.urlType,
          self.wellFormatMode,
          self.indentChar
        );
      },
      beforeSetHtml: function(html) {
        html = _formatHtml(
          html,
          self.filterMode ? self.htmlTags : null,
          "",
          false
        );
        return self.beforeSetHtml(html);
      },
      afterSetHtml: function() {
        self.edit = edit = this;
        self.afterSetHtml();
      },
      afterCreate: function() {
        self.edit = edit = this;
        self.cmd = edit.cmd;
        // hide menu when click document
        self._docMousedownFn = function(e) {
          if (self.menu) {
            self.hideMenu();
          }
        };
        K(edit.doc, document).mousedown(self._docMousedownFn);
        _bindContextmenuEvent.call(self);
        _bindNewlineEvent.call(self);
        _bindTabEvent.call(self);
        _bindFocusEvent.call(self);
        // afterChange event
        edit.afterChange(function(e) {
          if (!edit.designMode) {
            return;
          }
          self.updateState();
          self.addBookmark();
          if (self.options.afterChange) {
            self.options.afterChange.call(self);
          }
        });
        edit.textarea.keyup(function(e) {
          if (!e.ctrlKey && !e.altKey && _INPUT_KEY_MAP[e.which]) {
            if (self.options.afterChange) {
              self.options.afterChange.call(self);
            }
          }
        });
        // readonly
        if (self.readonlyMode) {
          self.readonly();
        }
        self.isCreated = true;
        if (self.initContent === "") {
          self.initContent = self.html();
        }
        // Bugfix: 全屏后和还原后光标没有选中之前光标的位置
        if (self._undoStack.length > 0) {
          var prev = self._undoStack.pop();
          if (prev.start) {
            self.html(prev.html);
            edit.cmd.range.moveToBookmark(prev);
            self.select();
          }
        }
        self.afterCreate();
        if (self.options.afterCreate) {
          self.options.afterCreate.call(self);
        }
      }
    }));
    // create statusbar
    statusbar
      .removeClass("statusbar")
      .addClass("ke-statusbar")
      .append('<span class="ke-inline-block ke-statusbar-center-icon"></span>')
      .append('<span class="ke-inline-block ke-statusbar-right-icon"></span>');

    // remove resize event
    if (self._fullscreenResizeHandler) {
      K(window).unbind("resize", self._fullscreenResizeHandler);
      self._fullscreenResizeHandler = null;
    }
    // reset size
    function initResize() {
      // Bugfix: 页面刷新后，与第一次访问加载的编译器高度不一致
      if (statusbar.height() === 0) {
        setTimeout(initResize, 100);
        return;
      }
      self.resize(width, height, false);
    }
    initResize();
    // fullscreen mode
    if (fullscreenMode) {
      self._fullscreenResizeHandler = function(e) {
        if (self.isCreated) {
          self.resize(
            _docElement().clientWidth,
            _docElement().clientHeight,
            false
          );
        }
      };
      K(window).bind("resize", self._fullscreenResizeHandler);
      toolbar.select("fullscreen");
      statusbar.first().css("visibility", "hidden");
      statusbar.last().css("visibility", "hidden");
    } else {
      if (_GECKO) {
        K(window).bind("scroll", function(e) {
          self._scrollPos = _getScrollPos();
        });
      }
      // bind drag event
      if (self.resizeType > 0) {
        _drag({
          moveEl: container,
          clickEl: statusbar,
          moveFn: function(x, y, width, height, diffX, diffY) {
            height += diffY;
            self.resize(null, height);
          }
        });
      } else {
        statusbar.first().css("visibility", "hidden");
      }
      if (self.resizeType === 2) {
        _drag({
          moveEl: container,
          clickEl: statusbar.last(),
          moveFn: function(x, y, width, height, diffX, diffY) {
            width += diffX;
            height += diffY;
            self.resize(width, height);
          }
        });
      } else {
        statusbar.last().css("visibility", "hidden");
      }
    }
    return self;
  },
  remove: function() {
    var self = this;
    if (!self.isCreated) {
      return self;
    }
    self.beforeRemove();

    self.srcElement.data("kindeditor", "");

    if (self.menu) {
      self.hideMenu();
    }
    _each(self.dialogs, function() {
      self.hideDialog();
    });
    K(document).unbind("mousedown", self._docMousedownFn);
    self.toolbar.remove();
    self.edit.remove();
    self.statusbar.last().unbind();
    self.statusbar.unbind();
    self.container.remove();
    self.container = self.toolbar = self.edit = self.menu = null;
    self.dialogs = [];
    self.isCreated = false;
    return self;
  },
  resize: function(width, height, updateProp) {
    var self = this;
    updateProp = _undef(updateProp, true);
    if (width) {
      if (!/%/.test(width)) {
        width = _removeUnit(width);
        width = width < self.minWidth ? self.minWidth : width;
      }
      self.container.css("width", _addUnit(width));
      if (updateProp) {
        self.width = _addUnit(width);
      }
    }
    if (height) {
      height = _removeUnit(height);
      editHeight =
        _removeUnit(height) -
        self.toolbar.div.height() -
        self.statusbar.height();
      editHeight = editHeight < self.minHeight ? self.minHeight : editHeight;
      self.edit.setHeight(editHeight);
      if (updateProp) {
        self.height = _addUnit(height);
      }
    }
    return self;
  },
  select: function() {
    this.isCreated && this.cmd.select();
    return this;
  },
  html: function(val) {
    var self = this;
    if (val === undefined) {
      return self.isCreated ? self.edit.html() : _elementVal(self.srcElement);
    }
    self.isCreated ? self.edit.html(val) : _elementVal(self.srcElement, val);
    // Bugfix: http://www.kindsoft.net/view.php?bbsid=4&postid=6703&pagenum=1
    if (self.isCreated) {
      self.cmd.selection();
    }
    return self;
  },
  fullHtml: function() {
    return this.isCreated ? this.edit.html(undefined, true) : "";
  },
  text: function(val) {
    var self = this;
    if (val === undefined) {
      return _trim(
        self
          .html()
          .replace(/<(?!img|embed).*?>/gi, "")
          .replace(/&nbsp;/gi, " ")
      );
    } else {
      return self.html(_escape(val));
    }
  },
  isEmpty: function() {
    return _trim(this.text().replace(/\r\n|\n|\r/, "")) === "";
  },
  isDirty: function() {
    return (
      _trim(this.initContent.replace(/\r\n|\n|\r|t/g, "")) !==
      _trim(this.html().replace(/\r\n|\n|\r|t/g, ""))
    );
  },
  selectedHtml: function() {
    var val = this.isCreated ? this.cmd.range.html() : "";
    val = _removeBookmarkTag(_removeTempTag(val));
    return val;
  },
  count: function(mode) {
    var self = this;
    mode = (mode || "html").toLowerCase();
    if (mode === "html") {
      return self.html().length;
    }
    if (mode === "text") {
      return self
        .text()
        .replace(/<(?:img|embed).*?>/gi, "K")
        .replace(/\r\n|\n|\r/g, "").length;
    }
    return 0;
  },
  exec: function(key) {
    key = key.toLowerCase();
    var self = this,
      cmd = self.cmd,
      changeFlag = _inArray(key, "selectall,copy,paste,print".split(",")) < 0;
    if (changeFlag) {
      self.addBookmark(false);
    }
    cmd[key].apply(cmd, _toArray(arguments, 1));
    // 不需要改变工具栏状态，不需要保存bookmark
    if (changeFlag) {
      self.updateState();
      self.addBookmark(false);
      if (self.options.afterChange) {
        self.options.afterChange.call(self);
      }
    }
    return self;
  },
  insertHtml: function(val, quickMode) {
    if (!this.isCreated) {
      return this;
    }
    val = this.beforeSetHtml(val);
    this.exec("inserthtml", val, quickMode);
    return this;
  },
  appendHtml: function(val) {
    this.html(this.html() + val);
    if (this.isCreated) {
      var cmd = this.cmd;
      cmd.range.selectNodeContents(cmd.doc.body).collapse(false);
      cmd.select();
    }
    return this;
  },
  sync: function() {
    _elementVal(this.srcElement, this.html());
    return this;
  },
  focus: function() {
    this.isCreated ? this.edit.focus() : this.srcElement[0].focus();
    return this;
  },
  blur: function() {
    this.isCreated ? this.edit.blur() : this.srcElement[0].blur();
    return this;
  },
  addBookmark: function(checkSize) {
    checkSize = _undef(checkSize, true);
    var self = this,
      edit = self.edit,
      body = edit.doc.body,
      html = _removeTempTag(body.innerHTML),
      bookmark;

    if (checkSize && self._undoStack.length > 0) {
      var prev = self._undoStack[self._undoStack.length - 1];
      if (
        Math.abs(html.length - _removeBookmarkTag(prev.html).length) <
        self.minChangeSize
      ) {
        return self;
      }
    }
    // 第一次执行addBookmark时不执行range.createBookmark
    if (edit.designMode && !self._firstAddBookmark) {
      var range = self.cmd.range;
      bookmark = range.createBookmark(true);
      bookmark.html = _removeTempTag(body.innerHTML);
      range.moveToBookmark(bookmark);
    } else {
      bookmark = {
        html: html
      };
    }
    self._firstAddBookmark = false;
    _addBookmarkToStack(self._undoStack, bookmark);
    return self;
  },
  undo: function() {
    return _undoToRedo.call(this, this._undoStack, this._redoStack);
  },
  redo: function() {
    return _undoToRedo.call(this, this._redoStack, this._undoStack);
  },
  fullscreen: function(bool) {
    this.fullscreenMode = bool === undefined ? !this.fullscreenMode : bool;
    this.addBookmark(false);
    return this.remove().create();
  },
  readonly: function(isReadonly) {
    isReadonly = _undef(isReadonly, true);
    var self = this,
      edit = self.edit,
      doc = edit.doc;
    if (self.designMode) {
      self.toolbar.disableAll(isReadonly, []);
    } else {
      _each(self.noDisableItems, function() {
        self.toolbar[isReadonly ? "disable" : "enable"](this);
      });
    }
    if (_IE) {
      doc.body.contentEditable = !isReadonly;
    } else {
      doc.designMode = isReadonly ? "off" : "on";
    }
    edit.textarea[0].disabled = isReadonly;
  },
  createMenu: function(options) {
    var self = this,
      name = options.name,
      knode = self.toolbar.get(name),
      pos = knode.pos();
    options.x = pos.x;
    options.y = pos.y + knode.height();
    options.z = self.options.zIndex;
    options.shadowMode = _undef(options.shadowMode, self.shadowMode);
    if (options.selectedColor !== undefined) {
      options.cls = "ke-colorpicker-" + self.themeType;
      options.noColor = self.lang("noColor");
      self.menu = _colorpicker(options);
    } else {
      options.cls = "ke-menu-" + self.themeType;
      options.centerLineMode = false;
      self.menu = _menu(options);
    }
    return self.menu;
  },
  hideMenu: function() {
    this.menu.remove();
    this.menu = null;
    return this;
  },
  hideContextmenu: function() {
    this.contextmenu.remove();
    this.contextmenu = null;
    return this;
  },
  createDialog: function(options) {
    var self = this,
      name = options.name;
    options.z = self.options.zIndex;
    options.shadowMode = _undef(options.shadowMode, self.shadowMode);
    options.closeBtn = _undef(options.closeBtn, {
      name: self.lang("close"),
      click: function(e) {
        self.hideDialog();
        // IE bugfix: 关闭dialog后，跳转到top
        if (_IE && self.cmd) {
          self.cmd.select();
        }
      }
    });
    options.noBtn = _undef(options.noBtn, {
      name: self.lang(options.yesBtn ? "no" : "close"),
      click: function(e) {
        self.hideDialog();
        // IE bugfix: 关闭dialog后，跳转到top
        if (_IE && self.cmd) {
          self.cmd.select();
        }
      }
    });
    if (self.dialogAlignType != "page") {
      options.alignEl = self.container;
    }
    options.cls = "ke-dialog-" + self.themeType;

    if (self.dialogs.length > 0) {
      var firstDialog = self.dialogs[0],
        parentDialog = self.dialogs[self.dialogs.length - 1];
      // 提高mask的z-index
      firstDialog.setMaskIndex(parentDialog.z + 2);
      // 提高dialog的z-index
      options.z = parentDialog.z + 3;
      // 不显示mask
      options.showMask = false;
    }
    var dialog = _dialog(options);
    self.dialogs.push(dialog);
    return dialog;
  },
  hideDialog: function() {
    var self = this;
    if (self.dialogs.length > 0) {
      self.dialogs.pop().remove();
    }
    if (self.dialogs.length > 0) {
      var firstDialog = self.dialogs[0],
        parentDialog = self.dialogs[self.dialogs.length - 1];
      // 降低mask的z-index
      firstDialog.setMaskIndex(parentDialog.z - 1);
    }
    return self;
  },
  errorDialog: function(html) {
    var self = this;
    var dialog = self.createDialog({
      width: 750,
      title: self.lang("uploadError"),
      body:
        '<div style="padding:10px 20px;"><iframe frameborder="0" style="width:708px;height:400px;"></iframe></div>'
    });
    var iframe = K("iframe", dialog.div),
      doc = K.iframeDoc(iframe);
    doc.open();
    doc.write(html);
    doc.close();
    K(doc.body).css("background-color", "#FFF");
    iframe[0].contentWindow.focus();
    return self;
  }
};

function _editor(options) {
  return new KEditor(options);
}

_instances = [];

/**
	@example
	K.create('textarea');
	K.create('textarea.class');
	K.create('#id');
*/
function _create(expr, options) {
  options = options || {};
  options.basePath = _undef(options.basePath, K.basePath);
  options.themesPath = _undef(options.themesPath, options.basePath + "themes/");
  options.langPath = _undef(options.langPath, options.basePath + "lang/");
  options.pluginsPath = _undef(
    options.pluginsPath,
    options.basePath + "plugins/"
  );
  // 自动加载CSS文件
  if (_undef(options.loadStyleMode, K.options.loadStyleMode)) {
    var themeType = _undef(options.themeType, K.options.themeType);
    _loadStyle(options.themesPath + "default/default.css");
    _loadStyle(options.themesPath + themeType + "/" + themeType + ".css");
  }
  function create(editor) {
    _each(_plugins, function(name, fn) {
      if (_isFunction(fn)) {
        fn.call(editor, KindEditor);
      }
    });
    return editor.create();
  }
  var knode = K(expr);
  if (!knode || knode.length === 0) {
    return;
  }
  if (knode.length > 1) {
    knode.each(function() {
      _create(this, options);
    });
    return _instances[0];
  }
  options.srcElement = knode[0];
  var editor = new KEditor(options);
  _instances.push(editor);
  // create editor
  if (_language[editor.langType]) {
    return create(editor);
  }
  // create editor after load lang file
  _loadScript(
    editor.langPath +
      editor.langType +
      ".js?ver=" +
      encodeURIComponent(K.DEBUG ? _TIME : _VERSION),
    function() {
      create(editor);
    }
  );
  return editor;
}

function _eachEditor(expr, fn) {
  K(expr).each(function(i, el) {
    K.each(_instances, function(j, editor) {
      if (editor && editor.srcElement[0] == el) {
        fn.call(editor, j);
        return false;
      }
    });
  });
}

K.remove = function(expr) {
  _eachEditor(expr, function(i) {
    this.remove();
    _instances.splice(i, 1);
  });
};

K.sync = function(expr) {
  _eachEditor(expr, function() {
    this.sync();
  });
};

K.html = function(expr, val) {
  _eachEditor(expr, function() {
    this.html(val);
  });
};

K.insertHtml = function(expr, val) {
  _eachEditor(expr, function() {
    this.insertHtml(val);
  });
};

K.appendHtml = function(expr, val) {
  _eachEditor(expr, function() {
    this.appendHtml(val);
  });
};

// 解决IE6浏览器重复下载背景图片的问题
if (_IE && _V < 7) {
  _nativeCommand(document, "BackgroundImageCache", true);
}

K.EditorClass = KEditor;
K.editor = _editor;
K.create = _create;
K.instances = _instances;
K.plugin = _plugin;
K.lang = _lang;

// core plugins
_plugin("core", function(K) {
  var self = this,
    shortcutKeys = {
      undo: "Z",
      redo: "Y",
      bold: "B",
      italic: "I",
      underline: "U",
      print: "P",
      selectall: "A"
    };
  // afterChange
  self.afterSetHtml(function() {
    if (self.options.afterChange) {
      self.options.afterChange.call(self);
    }
  });
  // sync
  self.afterCreate(function() {
    if (self.syncType != "form") {
      return;
    }
    var el = K(self.srcElement),
      hasForm = false;
    while ((el = el.parent())) {
      if (el.name == "form") {
        hasForm = true;
        break;
      }
    }
    if (hasForm) {
      el.bind("submit", function(e) {
        self.sync();
        // Bugfix: 	Firefox下后退，编辑器数据不保存
        K(window).bind("unload", function() {
          self.edit.textarea.remove();
        });
      });
      var resetBtn = K('[type="reset"]', el);
      resetBtn.click(function() {
        self.html(self.initContent);
        self.cmd.selection();
      });
      self.beforeRemove(function() {
        el.unbind();
        resetBtn.unbind();
      });
    }
  });
  // source
  self.clickToolbar("source", function() {
    if (self.edit.designMode) {
      self.toolbar.disableAll(true);
      self.edit.design(false);
      self.toolbar.select("source");
    } else {
      self.toolbar.disableAll(false);
      self.edit.design(true);
      self.toolbar.unselect("source");
      // Bugfix: http://www.kindsoft.net/view.php?bbsid=4&postid=7061&pagenum=1
      if (_GECKO) {
        setTimeout(function() {
          self.cmd.selection();
        }, 0);
      } else {
        self.cmd.selection();
      }
    }
    self.designMode = self.edit.designMode;
  });
  self.afterCreate(function() {
    if (!self.designMode) {
      self.toolbar.disableAll(true).select("source");
    }
  });
  // fullscreen
  self.clickToolbar("fullscreen", function() {
    self.fullscreen();
  });
  if (self.fullscreenShortcut) {
    var loaded = false;
    self.afterCreate(function() {
      K(self.edit.doc, self.edit.textarea).keyup(function(e) {
        if (e.which == 27) {
          // bugfix: 在opera 11上无法全屏，必须用setTimeout
          setTimeout(function() {
            self.fullscreen();
          }, 0);
        }
      });
      if (loaded) {
        // bugfix: 在IE上在代码模式下切换全屏出现奇怪现象
        if (_IE && !self.designMode) {
          return;
        }
        self.focus();
      }
      if (!loaded) {
        loaded = true;
      }
    });
  }
  // undo, redo
  _each("undo,redo".split(","), function(i, name) {
    if (shortcutKeys[name]) {
      self.afterCreate(function() {
        _ctrl(this.edit.doc, shortcutKeys[name], function() {
          self.clickToolbar(name);
        });
      });
    }
    self.clickToolbar(name, function() {
      self[name]();
    });
  });
  // formatblock
  self.clickToolbar("formatblock", function() {
    var blocks = self.lang("formatblock.formatBlock"),
      heights = {
        h1: 28,
        h2: 24,
        h3: 18,
        H4: 14,
        p: 12
      },
      curVal = self.cmd.val("formatblock"),
      menu = self.createMenu({
        name: "formatblock",
        width: self.langType == "en" ? 200 : 150
      });
    _each(blocks, function(key, val) {
      var style = "font-size:" + heights[key] + "px;";
      if (key.charAt(0) === "h") {
        style += "font-weight:bold;";
      }
      menu.addItem({
        title:
          '<span style="' + style + '" unselectable="on">' + val + "</span>",
        height: heights[key] + 12,
        checked: curVal === key || curVal === val,
        click: function() {
          self
            .select()
            .exec("formatblock", "<" + key + ">")
            .hideMenu();
        }
      });
    });
  });
  // fontname
  self.clickToolbar("fontname", function() {
    var curVal = self.cmd.val("fontname"),
      menu = self.createMenu({
        name: "fontname",
        width: 150
      });
    _each(self.lang("fontname.fontName"), function(key, val) {
      menu.addItem({
        title:
          '<span style="font-family: ' +
          key +
          ';" unselectable="on">' +
          val +
          "</span>",
        checked: curVal === key.toLowerCase() || curVal === val.toLowerCase(),
        click: function() {
          self.exec("fontname", key).hideMenu();
        }
      });
    });
  });
  // fontsize
  self.clickToolbar("fontsize", function() {
    var curVal = self.cmd.val("fontsize"),
      menu = self.createMenu({
        name: "fontsize",
        width: 150
      });
    _each(self.fontSizeTable, function(i, val) {
      menu.addItem({
        title:
          '<span style="font-size:' +
          val +
          ';" unselectable="on">' +
          val +
          "</span>",
        height: _removeUnit(val) + 12,
        checked: curVal === val,
        click: function() {
          self.exec("fontsize", val).hideMenu();
        }
      });
    });
  });
  // forecolor,hilitecolor
  _each("forecolor,hilitecolor".split(","), function(i, name) {
    self.clickToolbar(name, function() {
      self.createMenu({
        name: name,
        selectedColor: self.cmd.val(name) || "default",
        colors: self.colorTable,
        click: function(color) {
          self.exec(name, color).hideMenu();
        }
      });
    });
  });
  // cut,copy,paste
  _each("cut,copy,paste".split(","), function(i, name) {
    self.clickToolbar(name, function() {
      self.focus();
      try {
        self.exec(name, null);
      } catch (e) {
        alert(self.lang(name + "Error"));
      }
    });
  });
  // about
  self.clickToolbar("about", function() {
    var html =
      '<div style="margin:20px;">' +
      "<div>KindEditor " +
      _VERSION +
      "</div>" +
      '<div>Copyright &copy; <a href="http://www.kindsoft.net/" target="_blank">kindsoft.net</a> All rights reserved.</div>' +
      "</div>";
    self.createDialog({
      name: "about",
      width: 350,
      title: self.lang("about"),
      body: html
    });
  });
  // link,image,flash,media,anchor
  self.plugin.getSelectedLink = function() {
    return self.cmd.commonAncestor("a");
  };
  self.plugin.getSelectedImage = function() {
    return _getImageFromRange(self.edit.cmd.range, function(img) {
      return !/^ke-\w+$/i.test(img[0].className);
    });
  };
  self.plugin.getSelectedFlash = function() {
    return _getImageFromRange(self.edit.cmd.range, function(img) {
      return img[0].className == "ke-flash";
    });
  };
  self.plugin.getSelectedMedia = function() {
    return _getImageFromRange(self.edit.cmd.range, function(img) {
      return img[0].className == "ke-media" || img[0].className == "ke-rm";
    });
  };
  self.plugin.getSelectedAnchor = function() {
    return _getImageFromRange(self.edit.cmd.range, function(img) {
      return img[0].className == "ke-anchor";
    });
  };
  _each("link,image,flash,media,anchor".split(","), function(i, name) {
    var uName = name.charAt(0).toUpperCase() + name.substr(1);
    _each("edit,delete".split(","), function(j, val) {
      self.addContextmenu({
        title: self.lang(val + uName),
        click: function() {
          self.loadPlugin(name, function() {
            self.plugin[name][val]();
            self.hideMenu();
          });
        },
        cond: self.plugin["getSelected" + uName],
        width: 150,
        iconClass: val == "edit" ? "ke-icon-" + name : undefined
      });
    });
    self.addContextmenu({ title: "-" });
  });
  // table
  self.plugin.getSelectedTable = function() {
    return self.cmd.commonAncestor("table");
  };
  self.plugin.getSelectedRow = function() {
    return self.cmd.commonAncestor("tr");
  };
  self.plugin.getSelectedCell = function() {
    return self.cmd.commonAncestor("td");
  };
  _each(
    (
      "prop,cellprop,colinsertleft,colinsertright,rowinsertabove,rowinsertbelow,rowmerge,colmerge," +
      "rowsplit,colsplit,coldelete,rowdelete,insert,delete"
    ).split(","),
    function(i, val) {
      var cond =
        _inArray(val, ["prop", "delete"]) < 0
          ? self.plugin.getSelectedCell
          : self.plugin.getSelectedTable;
      self.addContextmenu({
        title: self.lang("table" + val),
        click: function() {
          self.loadPlugin("table", function() {
            self.plugin.table[val]();
            self.hideMenu();
          });
        },
        cond: cond,
        width: 170,
        iconClass: "ke-icon-table" + val
      });
    }
  );
  self.addContextmenu({ title: "-" });
  // other
  _each(
    (
      "selectall,justifyleft,justifycenter,justifyright,justifyfull,insertorderedlist," +
      "insertunorderedlist,indent,outdent,subscript,superscript,hr,print," +
      "bold,italic,underline,strikethrough,removeformat,unlink"
    ).split(","),
    function(i, name) {
      if (shortcutKeys[name]) {
        self.afterCreate(function() {
          _ctrl(this.edit.doc, shortcutKeys[name], function() {
            self.cmd.selection();
            self.clickToolbar(name);
          });
        });
      }
      self.clickToolbar(name, function() {
        self.focus().exec(name, null);
      });
    }
  );
  // paste
  self.afterCreate(function() {
    var doc = self.edit.doc,
      cmd,
      bookmark,
      div,
      cls = "__kindeditor_paste__",
      pasting = false;
    function movePastedData() {
      cmd.range.moveToBookmark(bookmark);
      cmd.select();
      if (_WEBKIT) {
        K("div." + cls, div).each(function() {
          K(this)
            .after("<br />")
            .remove(true);
        });
        K("span.Apple-style-span", div).remove(true);
        K("span.Apple-tab-span", div).remove(true);
        // Bugfix: https://github.com/kindsoft/kindeditor/issues/8
        K("span[style]", div).each(function() {
          if (K(this).css("white-space") == "nowrap") {
            K(this).remove(true);
          }
        });
        K("meta", div).remove();
      }
      var html = div[0].innerHTML;
      div.remove();
      if (html === "") {
        return;
      }
      // Chrome纯文本粘贴问题
      if (_WEBKIT) {
        html = html.replace(/(<br>)\1/gi, "$1");
      }
      // paste HTML
      if (self.pasteType === 2) {
        // 去除内容为空的p标签
        html = html.replace(/(<(?:p|p\s[^>]*)>) *(<\/p>)/gi, "");
        // paste from ms word
        if (/schemas-microsoft-com|worddocument|mso-\w+/i.test(html)) {
          html = _clearMsWord(
            html,
            self.filterMode ? self.htmlTags : K.options.htmlTags
          );
        } else {
          html = _formatHtml(html, self.filterMode ? self.htmlTags : null);
          html = self.beforeSetHtml(html);
        }
      }
      // paste text
      if (self.pasteType === 1) {
        html = html.replace(/&nbsp;/gi, " ");
        html = html.replace(/\n\s*\n/g, "\n");
        html = html.replace(/<br[^>]*>/gi, "\n");
        html = html.replace(/<\/p><p[^>]*>/gi, "\n");
        html = html.replace(/<[^>]+>/g, "");
        html = html.replace(/ {2}/g, " &nbsp;");
        if (self.newlineTag == "p") {
          if (/\n/.test(html)) {
            html = html
              .replace(/^/, "<p>")
              .replace(/$/, "<br /></p>")
              .replace(/\n/g, "<br /></p><p>");
          }
        } else {
          html = html.replace(/\n/g, "<br />$&");
        }
      }
      self.insertHtml(html, true);
    }
    K(doc.body).bind("paste", function(e) {
      if (self.pasteType === 0) {
        e.stop();
        return;
      }
      if (pasting) {
        return;
      }
      pasting = true;
      K("div." + cls, doc).remove();
      cmd = self.cmd.selection();
      bookmark = cmd.range.createBookmark();
      div = K('<div class="' + cls + '"></div>', doc).css({
        position: "absolute",
        width: "1px",
        height: "1px",
        overflow: "hidden",
        left: "-1981px",
        top: K(bookmark.start).pos().y + "px",
        "white-space": "nowrap"
      });
      K(doc.body).append(div);
      if (_IE) {
        var rng = cmd.range.get(true);
        rng.moveToElementText(div[0]);
        rng.select();
        rng.execCommand("paste");
        e.preventDefault();
      } else {
        cmd.range.selectNodeContents(div[0]);
        cmd.select();
      }
      setTimeout(function() {
        movePastedData();
        pasting = false;
      }, 0);
    });
  });
  // 取得HTML前执行
  self.beforeGetHtml(function(html) {
    // Bugifx : https://github.com/kindsoft/kindeditor/issues/66
    if (_IE && _V <= 8) {
      html = html.replace(
        /<div\s+[^>]*data-ke-input-tag="([^"]*)"[^>]*>([\s\S]*?)<\/div>/gi,
        function(full, tag) {
          return unescape(tag);
        }
      );
      // Bugfix: https://github.com/kindsoft/kindeditor/issues/88
      html = html.replace(/(<input)((?:\s+[^>]*)?>)/gi, function($0, $1, $2) {
        if (!/\s+type="[^"]+"/i.test($0)) {
          return $1 + ' type="text"' + $2;
        }
        return $0;
      });
    }
    return html
      .replace(
        /(<(?:noscript|noscript\s[^>]*)>)([\s\S]*?)(<\/noscript>)/gi,
        function($0, $1, $2, $3) {
          return $1 + _unescape($2).replace(/\s+/g, " ") + $3;
        }
      )
      .replace(/<img[^>]*class="?ke-(flash|rm|media)"?[^>]*>/gi, function(
        full
      ) {
        var imgAttrs = _getAttrList(full);
        var styles = _getCssList(imgAttrs.style || "");
        var attrs = _mediaAttrs(imgAttrs["data-ke-tag"]);
        var width = _undef(styles.width, "");
        var height = _undef(styles.height, "");
        if (/px/i.test(width)) {
          width = _removeUnit(width);
        }
        if (/px/i.test(height)) {
          height = _removeUnit(height);
        }
        attrs.width = _undef(imgAttrs.width, width);
        attrs.height = _undef(imgAttrs.height, height);
        return _mediaEmbed(attrs);
      })
      .replace(/<img[^>]*class="?ke-anchor"?[^>]*>/gi, function(full) {
        var imgAttrs = _getAttrList(full);
        return '<a name="' + unescape(imgAttrs["data-ke-name"]) + '"></a>';
      })
      .replace(
        /<div\s+[^>]*data-ke-script-attr="([^"]*)"[^>]*>([\s\S]*?)<\/div>/gi,
        function(full, attr, code) {
          return (
            "<script" + unescape(attr) + ">" + unescape(code) + "</script>"
          );
        }
      )
      .replace(
        /<div\s+[^>]*data-ke-noscript-attr="([^"]*)"[^>]*>([\s\S]*?)<\/div>/gi,
        function(full, attr, code) {
          return (
            "<noscript" + unescape(attr) + ">" + unescape(code) + "</noscript>"
          );
        }
      )
      .replace(/(<[^>]*)data-ke-src="([^"]*)"([^>]*>)/gi, function(
        full,
        start,
        src,
        end
      ) {
        full = full.replace(/(\s+(?:href|src)=")[^"]*(")/i, function(
          $0,
          $1,
          $2
        ) {
          return $1 + _unescape(src) + $2;
        });
        full = full.replace(/\s+data-ke-src="[^"]*"/i, "");
        return full;
      })
      .replace(/(<[^>]+\s)data-ke-(on\w+="[^"]*"[^>]*>)/gi, function(
        full,
        start,
        end
      ) {
        return start + end;
      });
  });
  // 设置HTML前执行
  self.beforeSetHtml(function(html) {
    // Bugifx : https://github.com/kindsoft/kindeditor/issues/66
    if (_IE && _V <= 8) {
      html = html.replace(
        /<input[^>]*>|<(select|button)[^>]*>[\s\S]*?<\/\1>/gi,
        function(full) {
          var attrs = _getAttrList(full);
          var styles = _getCssList(attrs.style || "");
          if (styles.display == "none") {
            return (
              '<div class="ke-display-none" data-ke-input-tag="' +
              escape(full) +
              '"></div>'
            );
          }
          return full;
        }
      );
    }
    return html
      .replace(/<embed[^>]*type="([^"]+)"[^>]*>(?:<\/embed>)?/gi, function(
        full
      ) {
        var attrs = _getAttrList(full);
        attrs.src = _undef(attrs.src, "");
        attrs.width = _undef(attrs.width, 0);
        attrs.height = _undef(attrs.height, 0);
        return _mediaImg(self.themesPath + "common/blank.gif", attrs);
      })
      .replace(/<a[^>]*name="([^"]+)"[^>]*>(?:<\/a>)?/gi, function(full) {
        var attrs = _getAttrList(full);
        if (attrs.href !== undefined) {
          return full;
        }
        return (
          '<img class="ke-anchor" src="' +
          self.themesPath +
          'common/anchor.gif" data-ke-name="' +
          escape(attrs.name) +
          '" />'
        );
      })
      .replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, function(
        full,
        attr,
        code
      ) {
        return (
          '<div class="ke-script" data-ke-script-attr="' +
          escape(attr) +
          '">' +
          escape(code) +
          "</div>"
        );
      })
      .replace(/<noscript([^>]*)>([\s\S]*?)<\/noscript>/gi, function(
        full,
        attr,
        code
      ) {
        return (
          '<div class="ke-noscript" data-ke-noscript-attr="' +
          escape(attr) +
          '">' +
          escape(code) +
          "</div>"
        );
      })
      .replace(/(<[^>]*)(href|src)="([^"]*)"([^>]*>)/gi, function(
        full,
        start,
        key,
        src,
        end
      ) {
        if (full.match(/\sdata-ke-src="[^"]*"/i)) {
          return full;
        }
        full =
          start +
          key +
          '="' +
          src +
          '"' +
          ' data-ke-src="' +
          _escape(src) +
          '"' +
          end;
        return full;
      })
      .replace(/(<[^>]+\s)(on\w+="[^"]*"[^>]*>)/gi, function(full, start, end) {
        return start + "data-ke-" + end;
      })
      .replace(/<table[^>]*\s+border="0"[^>]*>/gi, function(full) {
        if (full.indexOf("ke-zeroborder") >= 0) {
          return full;
        }
        return _addClassToTag(full, "ke-zeroborder");
      });
  });
});
